use serde::{Deserialize, Deserializer, Serialize, Serializer};
use share::core::device_id;
use share::core::device_id::DeviceId;
use snafu::{ResultExt, Snafu};
use std::net::IpAddr;
use std::str::FromStr;

/// 白名单的键
#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub enum WhitelistKey {
    Ip(IpAddr),
    DeviceId(DeviceId),
}

#[derive(Debug, Snafu)]
pub enum Error {
    #[snafu(display("The structure of {content} should be ip:???? or device_id:????"))]
    Structure { content: String },

    #[snafu(display("The ip string in {content} is not a valid IP address"))]
    Ip {
        content: String,
        source: std::net::AddrParseError,
    },

    #[snafu(display("The device id string in {content} is not a valid device id"))]
    Device {
        content: String,
        source: device_id::Error,
    },
}

impl TryFrom<String> for WhitelistKey {
    type Error = Error;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        if let Some(ip_str) = value.strip_prefix("ip:") {
            let ip = IpAddr::from_str(ip_str).context(IpSnafu {
                content: value.clone(),
            })?;
            Ok(WhitelistKey::Ip(ip))
        } else if let Some(id) = value.strip_prefix("device_id:") {
            DeviceId::new(id)
                .map(WhitelistKey::DeviceId)
                .context(DeviceSnafu {
                    content: value.clone(),
                })
        } else {
            Err(Error::Structure {
                content: value.clone(),
            })?
        }
    }
}

impl FromStr for WhitelistKey {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        WhitelistKey::try_from(s.to_string())
    }
}

impl Serialize for WhitelistKey {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match self {
            WhitelistKey::Ip(ip) => serializer.serialize_str(&format!("ip:{}", ip)),
            WhitelistKey::DeviceId(id) => serializer.serialize_str(&format!("device_id:{}", id)),
        }
    }
}

impl<'de> Deserialize<'de> for WhitelistKey {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        WhitelistKey::try_from(s).map_err(serde::de::Error::custom)
    }
}
